Script description: This script manipulates the raw data of the retrieved datasets to show summary tables and figures.

Script structure:

  1. Number of datasets and relevance categories.2. Queries performance - 3. Temporal duration - 4. Spatial range - 5. Temporal duration, spatial range, and relevance - 6. EBV data types - 7. Data format - 8. Datasets accessibility and relevance per year

(TODO)

  1. Journals

  2. Taxonomic coverage

  3. Dataset location

  4. Temporal and spatial information location

library(dplyr)
library(reshape)
library(ggplot2)
library(hrbrthemes)
library(ggpubr)
library(tidyr)
library(stringr)
library(ggrepel)
library(readxl)

source("Functions.R")

Some parameters for the plots:

my.theme<-theme(axis.text=element_text(size=15),
        axis.title = element_text(size = 17),
        legend.text=element_text(size=10),
        legend.title = element_text(size=12),
        plot.title = element_text(face="bold",size=14,margin=margin(0,0,20,0),hjust = 0.5),
        axis.title.y = element_text(margin = margin(t = 0, r = 10, b = 0, l = 0)),
        axis.title.x = element_text(margin = margin(t = 15, r = 0, b = 0, l = 0)),
        panel.background = element_rect(fill = 'white'))

Read data


#dataset <- read.csv("repo_datasets.csv", header = TRUE,sep=";")
dataset <- read_excel("df_repository.xlsx")
table(dataset$valid_yn)

 no yes 
 53 108 

table(dataset$reason_non_valid[which(dataset$valid_yn=="no")])

                location     non-field experiment non-relevant information           non biological         not in the field 
                      37                        4                        2                        1                        3 
                   other 
                       6 

Eliminate empty rows and those where dataset_relevance is left in blank (they correspond to referred publications in the retrieved datasets and are not used in these analyses):

dataset <- dataset[dataset$dataset_relevance != "",]
dataset <- dataset %>% dplyr::filter(valid_yn == "yes")
dataset$dataset_relevance <- as.factor(dataset$dataset_relevance)

dataset <- dataset[dataset$source != "semantic_scholar",]
dataset$id_query  <- stringr::str_trim(dataset$id_query )
dataset$dataset_relevance  <- str_trim(dataset$dataset_relevance )
head(dataset)

1. Number of datasets and relevance categories



df_N_relevance <- count_by_relevance(dataset)

df_N_relevance

1.1 Relevance counts by source


df_rel_source <- count_relevance_by_source(df = dataset)

df_rel_source
NA
NA

Plot the results:


df_rel_source_melt <- melt(df_rel_source, id ="relevance")
Error in data.frame(ids, x, data[, x]) : 
  les arguments impliquent des nombres de lignes différents : 0, 1

2. Queries performance

2.1. N publications and relevance categories per query

Grouped queries


dataset$id_query <- as.factor(dataset$id_query)
df_queries_counts <- compute_df_n_relevance_queries(data = dataset)

df_queries_counts

Plot the results:

df_queries_counts_all <- dataset %>% 
  group_by(dataset_relevance, id_query) %>% 
  summarise(n = n()) 
`summarise()` has grouped output by 'dataset_relevance'. You can override using the `.groups` argument.
df_queries_counts_all <- df_queries_counts_all %>% dplyr::filter(dataset_relevance %in% c("X", "L", "M", "H")) %>% mutate(dataset_relevance = factor(dataset_relevance))

plot_queries_relevance_counts <- ggplot(df_queries_counts_all, 
                                          aes(x=id_query, y=n, group = dataset_relevance, 
                                              color = dataset_relevance,shape=dataset_relevance )) +
  geom_segment( aes(x=id_query ,xend=id_query, y=0, yend=max(n)), color="grey") +
    geom_point(size=4, alpha = 0.8) +
    coord_flip() +
    # theme_ipsum() +
    theme(
      panel.grid.minor.y = element_blank(),
      panel.grid.major.y = element_blank(),
      legend.position="top"
    ) +
    xlab("Query ID")+
    ylab("publications counts") +
    scale_color_manual(values = c( "red","purple2", "dodgerblue", "black"))+
    scale_shape_manual(values = c(19, 19, 19, 1))+
    my.theme
  • Query 4 delivers the highest number of relevant datasets (highest number of High, Moderate and Low relevance datasets), but also retrieves by far the higher amount of publication.

  • Some queries do not add any relevant publication (e.g. “0,6,5,7” or “7,2,9”).

Individual queries


convert_query = data.frame("id_query" = as.character(seq(0,10)), "query" = c(
"survey + species",
"time series + species",
"inventory + species",
"species",
"abundance + species",
"occurrence + species",
"population + species",
"sites + species",
"sampling + species",
"collection + species",
"density + species"
  
))


dataset_q

#split rows in function of character comas

dataset_q <- dataset %>%               
  separate_rows(id_query, sep=",") 
dataset_q <- merge(dataset_q, convert_query, by.all="id_query")

dataset_q$query  <- factor(dataset_q$query,
                              levels= c("species", "occurrence + species", "inventory + species", "collection + species", "sampling + species", "survey + species" , "population + species", "sites + species", "density + species", "abundance + species" , "time series + species" ))
dataset_q
NA
df_queries_counts_ind <- dataset_q %>% 
  group_by(dataset_relevance, query) %>% 
  summarise(n = n()) 
`summarise()` has grouped output by 'dataset_relevance'. You can override using the `.groups` argument.
df_queries_counts_ind <- df_queries_counts_ind %>% dplyr::filter(dataset_relevance %in% c("X", "L", "M", "H")) %>% mutate(dataset_relevance = factor(dataset_relevance))

plot_queries_relevance_counts <- ggplot(df_queries_counts_ind, 
                                          aes(x=query, y=n, group = dataset_relevance, 
                                              color = dataset_relevance,shape=dataset_relevance )) +
  geom_segment( aes(x=query ,xend=query, y=0, yend=max(n)), color="grey") +
    geom_point(size=4, alpha = 0.8) +
    coord_flip() +
    # theme_ipsum() +
    theme(
      panel.grid.minor.y = element_blank(),
      panel.grid.major.y = element_blank(),
      legend.position="top"
    ) +
    xlab("Query ID")+
    ylab("publications counts") +
    scale_color_manual(values = c( "red","purple2", "dodgerblue", "black"))+
    scale_shape_manual(values = c(19, 19, 19, 1))+
    my.theme

plot_queries_relevance_counts

NA
NA
  • query 4 shows the highest number of relevant datasets for high, moderate and low relevance datasets, but retrieves almost double number of articles (~70) compared to the rest of queries.

  • queries 5 and 7 are able to retrieve some relevant datasets with ~25 publications retrieved.

2.2 Classify queries by their relevance (index)

Puctuations for the relevance categories:

  • High = 5
  • Mod = 2
  • Low = 0.5
  • non_relevant = 0

Index 1: is the result of summing the puntuations assigned to each relevance category

Index 1 = sum punctuations = (N High * 5) + (N Mod * 2) + (N Low * 0.5)

Index 2: is the Index1 but accounting for the total number of publications retrieved by the query.

Index 2 = sum punctuations / n publications retrieved

Grouped queries


df_queries_counts_index <- compute_index12(df_counts = df_queries_counts, order_based_index = "2") # "1" for ordering according to index 1, "2" for index 2

df_queries_counts_index

Queries 3, 4, 0, and 6 are the top 4 index1 values. Queries 3 and 4 have far higher index than the rest.

Plot the results:


plot_group.queries_index12 <- plot_queries_index(df = df_queries_counts_index)

plot_group.queries_index12

#ggsave("plots_repo/plot_group.queries_index12.png", height = 5, width = 11)
  • Index 1 shows that queries “3” and “4” offer the higher perfomance, followed by “6” and “5”.

  • Index 2 shows that querie “6,7,5” offer the highest performance, followed by “7,8”, “6,7”, and “5,6,7,8,9”

individual queries

df_queries_counts_index_ind <- compute_index12(df_counts = df_queries_counts_ind, order_based_index = "2") # "1" for ordering according to index 1, "2" for index 2

df_queries_counts_index_ind

Plot the results

plot_queries_index12_ind <- plot_queries_index(df = df_queries_counts_index_ind)

plot_queries_index12_ind

#ggsave("plots_repo/plot_ind.queries_index12.png", height = 5, width = 11)

2.3. F SCORE

Precision Recall

Binary view: M,H relevant | L,X unrevelant

N relevant datasets -> True Positives (TP)

N unrelevant datasets -> False Positives (FP)

Sum of all relevant publications of the dataset that are not detected = FN

PRECISION = TP / TP + FP

RECALL = TP / TP + FN

F SCORE = 2* precision * recall / precision + recall

Split grouped queries into individual ones


df_zscores_queries <- calculate_z.score_queries(df = dataset)

df_zscores_queries
NA

Plot results

plot_queries_scores <- ggplot(df_zscores_queries, aes(x = Precision, y = Recall, colour = Fscore)) +
  geom_point(size = 10)+
  geom_label_repel(aes(label = queries))+
  theme_bw()+
  my.theme

plot_queries_scores

#ggsave("plots_repo/plot_queries_scores.png", height = 7, width = 7)

3. Temporal duration

3.1 Temporal duration counts

How many publications without temporal duration data?

nNa <- count_not.reported_temporal.duration(dataset)

print(paste(nNa, "publications without temporal duration data"))

Publication counts by temporal duration (years):

  
df_duration_counts <- count_durations(df = dataset, order_by = "counts")

df_duration_counts

Plot the results:

plot_temp_duration <- plot_duration_counts(df = df_duration_counts, 
                                           counts_Na = nNa)

plot_temp_duration

#ggsave("plots_repo/plot_temp_duration.png", height = 7, width = 12)

Average duration (for those with data):


dataset_temp_duration <- subset(dataset, 
                                  temporal_duration_y > 0 | temporal_duration_y == "no",
                                  select = c(temporal_duration_y,id_query))
  
  dataset_temp_duration$temporal_duration_y <- as.numeric(dataset_temp_duration$temporal_duration_y)
  
  

print(paste("mean of",
            round(mean(na.omit(dataset_temp_duration$temporal_duration_y)),digits = 1),
            "years"))

3.1 Temporal duration counts per relevance category



plot_duration_relevance.categories <- plot_duration_relevance(df = dataset,
                                                              counts_Na = nNa)

plot_duration_relevance.categories

ggsave("plots_repo/plot_duration_relevance.categories.png", height = 7, width = 9)

4. Spatial range

4.1 count spatial range publications

WARNING: publications that cant be accessed are not counted

How many publications without spatial range data?


n_not_reported <- count_not.reported_spatial_range(dataset)

print(paste(n_not_reported, "publications without spatial range data"))

Spatial ranges are divided according to the thresholds established to determine a low, moredate and high spatial range: <5000, 500-15000, >15000


plot_spatial_range_counts <- plot_spat.range_counts(dataset)

plot_spatial_range_counts

#ggsave("plots_repo/plot_spatial_range_counts.png", height = 7, width = 7)

Average spatial range (for those with data):


dataset1 <- dataset[dataset$dataset_relevance != "cant access",]
  
  dataset1$dataset_relevance <- as.factor(dataset1$dataset_relevance)
  
  spatial_range_km2_vec <- dataset1$spatial_range_km2[dataset1$spatial_range_km2 != ""]
  
  spatial_range_km2_vec <- spatial_range_km2_vec[!is.na(spatial_range_km2_vec)]
  
  spatial_range_km2_vec <- as.numeric(spatial_range_km2_vec) 


print(paste("mean of",
            round(mean(na.omit(spatial_range_km2_vec)), digits = 0),
            "km2"))

5. Temporal duration, spatial range, relevance


plot_spatial_temporal_relevance <- plot_spat_temp_relevance(df = dataset)

plot_spatial_temporal_relevance

#ggsave("plots_repo/plot_spatial_temporal_relevance.png", height = 7, width = 9)

6. EBV data types


df_data_type_counts <- compute_df_data.type(df = dataset)

df_data_type_counts

Plot the results


plot_data_type <- plot_data.type_counts(df_data_type_counts)

plot_data_type

#ggsave("plots_repo/plot_data_type.png", height = 7, width = 9)

7. Data format

Those datasets in the repository that are relevance category X don’t have format information.


plot_data_format_counts <- plot_data.type_format(dataset)

plot_data_format_counts

#ggsave("plots_repo/plot_data_format_counts.png", height = 7, width = 9)

8. Datasets accessibility and relevance per year

Raw plot relevance per year


plot_relevance_access_year <- plot_relevance_year(dataset)

plot_relevance_access_year

#ggsave("plots_repo/plot_relevance_access_year.png", height = 7, width = 14)

Relevance per year (intervals):


df_relevance_year.range <- compute_df_relevance_year.range(dataset)

df_relevance_year.range

Plot the results:


plot_relevance_year.range <- plot_relevance_year.range_repo(dataset)

plot_relevance_year.range

#ggsave("plots_repo/plot_relevance_year.range.png", height = 7, width = 9)

9. Journals

Count publications per journal


dataset.j <- dataset[dataset$Journal != "" & dataset$Journal != "no",]

df_journals_counts <- as.data.frame(table(dataset.j$Journal))

colnames(df_journals_counts) = c("Journal", "Publication_counts")

df_journals_counts <- df_journals_counts[order(-df_journals_counts$Publication_counts),]


df_journals_counts

print(paste("A total of", length(df_journals_counts$Journal), "journals" ))

10. Source of information across time


df.source_time <- df_source_time(df = dataset)

df.source_time
df_plot_source_time <- as.data.frame(table(df.source_time$year,df.source_time$source ))

colnames(df_plot_source_time) <- c("year", "source", "N")

df_plot_source_time$N <- as.numeric(df_plot_source_time$N)


plot_source_time <- ggplot(df_plot_source_time, aes(x = year, y = N, group = source, color = source)) +
  geom_line(size = 1)+
    theme_bw()+
  my.theme+
  theme(axis.text.x = element_text(angle = 0, hjust=0.95,vjust=0.2, size = 9))+
  ylab("N retrieved articles") +
  scale_x_discrete(guide = guide_axis(n.dodge=2))

plot_source_time


#ggsave("plots_repo/plot_source_time.png", height = 7, width = 13)

10. Location of information


df_location_info <- compute_df_location_info(df = dataset)
df_plot_loc_spatial.range <- df_location_info[df_location_info$spatial_range_position != "",]

df_plot_loc_temporal.range <- df_location_info[df_location_info$temporal_range_position != "",]

df_plot_loc_temporal.duration <- df_location_info[df_location_info$temporal_duration_position != "",]



plot_spat_range_position <- ggplot(na.omit(df_plot_loc_spatial.range), aes(x=spatial_range_position, fill = dataset_location)) + 
  geom_bar(aes(y = (..count..))) +
  geom_text(stat='count', aes(label=..count..),position = position_stack(vjust = 0.5))+
  ggtitle("Spatial range information")+
  theme_bw()+
  my.theme+
  theme(axis.text.x = element_text(angle = 0, hjust=0.95,vjust=0.2, size = 9))+
  ylab("N retrieved articles") +
  scale_x_discrete(guide = guide_axis(n.dodge=2))


plot_temp_range_position <- ggplot(na.omit(df_plot_loc_temporal.range), aes(x=temporal_range_position, fill = dataset_location)) + 
  geom_bar(aes(y = (..count..))) +
  geom_text(stat='count', aes(label=..count..),position = position_stack(vjust = 0.5))+
    ggtitle("Temporal range information")+
  theme_bw()+
  my.theme+
  theme(axis.text.x = element_text(angle = 0, hjust=0.95,vjust=0.2, size = 9))+
  ylab("N retrieved articles") +
  scale_x_discrete(guide = guide_axis(n.dodge=2))


df_plot_loc_temporal.duration <- ggplot(na.omit(df_plot_loc_temporal.duration), aes(x=temporal_duration_position, fill = dataset_location)) + 
  geom_bar(aes(y = (..count..))) +
  geom_text(stat='count', aes(label=..count..),position = position_stack(vjust = 0.5))+
  ggtitle("Temporal duration information")+
  theme_bw()+
  my.theme+
  theme(axis.text.x = element_text(angle = 0, hjust=0.95,vjust=0.2, size = 9))+
  ylab("N retrieved articles") +
  scale_x_discrete(guide = guide_axis(n.dodge=2))


plot_location_spatiotemporal_information <- ggarrange(plot_spat_range_position,
          plot_temp_range_position,
          df_plot_loc_temporal.duration,
          ncol = 1,
          nrow = 3, 
          common.legend = TRUE,
          legend = "top")


plot_location_spatiotemporal_information


#ggsave("plots_repo/plot_location_spatiotemporal_information.png", height = 16, width = 7)

location - dataset type

We evaluate the feasibility to automatically retrieve the information on the dataset type by looking at whether authors make explicit the type of dataset in either the title or abstract.

Type of dataset should fall in one of the following categories or synonyms:

df_dataset_types <- read.csv("../data/dataset_types.csv", sep = ";")
df_dataset_types

I will add the following synonyms:

presence-absence: detection, capture

EBV genetic analyses: 16S, 18S, genetic data, microsatelites, barcodes(?), haplotypes, eDNA, SNPs

df_dataset_types[nrow(df_dataset_types)+1,] <- c("presence-absence", "detection", "synonym")
df_dataset_types[nrow(df_dataset_types)+1,] <- c("presence-absence", "capture", "synonym")

df_dataset_types[nrow(df_dataset_types)+1,] <- c("EBV genetic analyses", "16S", "methods")
df_dataset_types[nrow(df_dataset_types)+1,] <- c("EBV genetic analyses", "18S", "methods")
df_dataset_types[nrow(df_dataset_types)+1,] <- c("EBV genetic analyses", "barcodes", "methods")
df_dataset_types[nrow(df_dataset_types)+1,] <- c("EBV genetic analyses", "haplotypes", "methods")
df_dataset_types[nrow(df_dataset_types)+1,] <- c("EBV genetic analyses", "eDNA", "methods")
df_dataset_types[nrow(df_dataset_types)+1,] <- c("EBV genetic analyses", "SNPs", "methods")

df_dataset_types

Detecting the words in title or abstract



dataset1 <- dataset[-which(is.na(dataset[,"description"])),]
dataset2 <- dataset1[-which(dataset1$title == ""),]

url <- c()
dataset_type <- c()
keyword_abs <- list()
keyword_title <- list()
#keyword_type <- c()
in_title <- c()
in_abstract <- c()



for (i in 1:nrow(dataset2)) {
  
  
  
  dataset_type[i] <- dataset2[i, "data_type"]
  url[i] <- dataset2[i, "url"]
  
  
  
  
  # search on the abstract and note the list of keywords that match the dataset types

  keyword_abs[[i]] <- unique(get_keywords(input_string = dataset2$description[i], 
               dataset_types =  df_dataset_types))
  
  # if the list of keywords has 1 or more entry, then note that we found information in the abstract
  
  if(length(keyword_abs[i][!sapply(keyword_abs[i], is.null)]) == 0){
    
    in_abstract[i] <- "no"
    
  } else if (length(keyword_abs[i][!sapply(keyword_abs[i], is.null)]) > 0){
    
    in_abstract[i] <- "yes"
    
  }
  
  
  

  # search on the title and note the list of keywords that match the dataset types

  keyword_title[[i]] <- unique(get_keywords(input_string = dataset2$title[i], 
               dataset_types =  df_dataset_types))
  
  # if the list of keywords has 1 or more entry, then note that we found information in the abstract
  
  if(length(keyword_title[i][!sapply(keyword_title[i], is.null)]) == 0){
    
    in_title[i] <- "no"
    
  } else if (length(keyword_title[i][!sapply(keyword_title[i], is.null)]) > 0){
    
    in_title[i] <- "yes"
    
  } 
  
  
  
}




#data.frame(dataset_type, keyword_abs, keyword_title, in_title, in_abstract)


df_data_type_abs <- data.frame(url, dataset_type, in_abstract, in_title)
df_data_type_abs$keyword_abs <- keyword_abs

#eliminate those datasets without data_type info

df_data_type_abs1 <- df_data_type_abs[df_data_type_abs$dataset_type != "",]

df_data_type_abs1

For some reason, keyword_title has 3 less entries that keyword_abstract, and I can’t figure out why. So for now I cant obtain the list of words that appear in the title.


print(paste(length(which(df_data_type_abs1$in_abstract == "yes")), 
            "with dataset type or synonym explicit in the abstract, out of", length(df_data_type_abs1$in_abstract)))

print(paste("so", 
      round(length(which(df_data_type_abs1$in_abstract == "yes"))/length(df_data_type_abs1$in_abstract)*100, 1),
"%"))
print(paste(length(which(df_data_type_abs1$in_title == "yes")), 
            "with dataset type or synonym explicit in the title, out of", length(df_data_type_abs1$in_title)))

print(paste("so", 
      round(length(which(df_data_type_abs1$in_title == "yes"))/length(df_data_type_abs1$in_title)*100, 1),
"%"))
LS0tDQp0aXRsZTogIk92ZXJ2aWV3IG9mIHJldHJpZXZlZCBkYXRhc2V0cyINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQoqKlNjcmlwdCBkZXNjcmlwdGlvbjoqKiBUaGlzIHNjcmlwdCBtYW5pcHVsYXRlcyB0aGUgcmF3IGRhdGEgb2YgdGhlIHJldHJpZXZlZCBkYXRhc2V0cyB0byBzaG93IHN1bW1hcnkgdGFibGVzIGFuZCBmaWd1cmVzLg0KDQoqKlNjcmlwdCBzdHJ1Y3R1cmU6KioNCg0KMS4gIE51bWJlciBvZiBkYXRhc2V0cyBhbmQgcmVsZXZhbmNlIGNhdGVnb3JpZXMuMi4gUXVlcmllcyBwZXJmb3JtYW5jZSAtIDMuIFRlbXBvcmFsIGR1cmF0aW9uIC0gNC4gU3BhdGlhbCByYW5nZSAtIDUuIFRlbXBvcmFsIGR1cmF0aW9uLCBzcGF0aWFsIHJhbmdlLCBhbmQgcmVsZXZhbmNlIC0gNi4gRUJWIGRhdGEgdHlwZXMgLSA3LiBEYXRhIGZvcm1hdCAtIDguIERhdGFzZXRzIGFjY2Vzc2liaWxpdHkgYW5kIHJlbGV2YW5jZSBwZXIgeWVhcg0KDQooVE9ETykNCg0KOS4gIEpvdXJuYWxzDQoNCjEwLiBUYXhvbm9taWMgY292ZXJhZ2UNCg0KMTEuIERhdGFzZXQgbG9jYXRpb24NCg0KMTIuIFRlbXBvcmFsIGFuZCBzcGF0aWFsIGluZm9ybWF0aW9uIGxvY2F0aW9uDQoNCmBgYHtyfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocmVzaGFwZSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoaHJicnRoZW1lcykNCmxpYnJhcnkoZ2dwdWJyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoZ2dyZXBlbCkNCmxpYnJhcnkocmVhZHhsKQ0KDQpzb3VyY2UoIkZ1bmN0aW9ucy5SIikNCmBgYA0KDQpTb21lIHBhcmFtZXRlcnMgZm9yIHRoZSBwbG90czoNCg0KYGBge3J9DQpteS50aGVtZTwtdGhlbWUoYXhpcy50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTE1KSwNCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTcpLA0KICAgICAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMCksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTEyKSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlPSJib2xkIixzaXplPTE0LG1hcmdpbj1tYXJnaW4oMCwwLDIwLDApLGhqdXN0ID0gMC41KSwNCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbih0ID0gMCwgciA9IDEwLCBiID0gMCwgbCA9IDApKSwNCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbih0ID0gMTUsIHIgPSAwLCBiID0gMCwgbCA9IDApKSwNCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gJ3doaXRlJykpDQoNCmBgYA0KDQoNCg0KDQoNCiMjIyMgUmVhZCBkYXRhDQoNCg0KYGBge3J9DQoNCiNkYXRhc2V0IDwtIHJlYWQuY3N2KCJyZXBvX2RhdGFzZXRzLmNzdiIsIGhlYWRlciA9IFRSVUUsc2VwPSI7IikNCmRhdGFzZXQgPC0gcmVhZF9leGNlbCgiZGZfcmVwb3NpdG9yeS54bHN4IikNCnRhYmxlKGRhdGFzZXQkdmFsaWRfeW4pDQpgYGANCmBgYHtyfQ0KDQp0YWJsZShkYXRhc2V0JHJlYXNvbl9ub25fdmFsaWRbd2hpY2goZGF0YXNldCR2YWxpZF95bj09Im5vIildKQ0KYGBgDQoNCkVsaW1pbmF0ZSBlbXB0eSByb3dzIGFuZCB0aG9zZSB3aGVyZSBkYXRhc2V0X3JlbGV2YW5jZSBpcyBsZWZ0IGluIGJsYW5rICh0aGV5IGNvcnJlc3BvbmQgdG8gcmVmZXJyZWQgcHVibGljYXRpb25zIGluIHRoZSByZXRyaWV2ZWQgZGF0YXNldHMgYW5kIGFyZSBub3QgdXNlZCBpbiB0aGVzZSBhbmFseXNlcyk6DQpgYGB7cn0NCmRhdGFzZXQgPC0gZGF0YXNldFtkYXRhc2V0JGRhdGFzZXRfcmVsZXZhbmNlICE9ICIiLF0NCmRhdGFzZXQgPC0gZGF0YXNldCAlPiUgZHBseXI6OmZpbHRlcih2YWxpZF95biA9PSAieWVzIikNCmRhdGFzZXQkZGF0YXNldF9yZWxldmFuY2UgPC0gYXMuZmFjdG9yKGRhdGFzZXQkZGF0YXNldF9yZWxldmFuY2UpDQoNCmRhdGFzZXQgPC0gZGF0YXNldFtkYXRhc2V0JHNvdXJjZSAhPSAic2VtYW50aWNfc2Nob2xhciIsXQ0KZGF0YXNldCRpZF9xdWVyeSAgPC0gc3RyaW5ncjo6c3RyX3RyaW0oZGF0YXNldCRpZF9xdWVyeSApDQpkYXRhc2V0JGRhdGFzZXRfcmVsZXZhbmNlICA8LSBzdHJfdHJpbShkYXRhc2V0JGRhdGFzZXRfcmVsZXZhbmNlICkNCmhlYWQoZGF0YXNldCkNCmBgYA0KDQoNCg0KIyMgMS4gTnVtYmVyIG9mIGRhdGFzZXRzIGFuZCByZWxldmFuY2UgY2F0ZWdvcmllcw0KDQoNCmBgYHtyfQ0KDQoNCmRmX05fcmVsZXZhbmNlIDwtIGNvdW50X2J5X3JlbGV2YW5jZShkYXRhc2V0KQ0KDQpkZl9OX3JlbGV2YW5jZQ0KYGBgDQoNCg0KDQojIyMgMS4xIFJlbGV2YW5jZSBjb3VudHMgYnkgc291cmNlDQoNCmBgYHtyfQ0KDQpkZl9yZWxfc291cmNlIDwtIGNvdW50X3JlbGV2YW5jZV9ieV9zb3VyY2UoZGYgPSBkYXRhc2V0KQ0KDQpkZl9yZWxfc291cmNlDQoNCg0KYGBgDQoNClBsb3QgdGhlIHJlc3VsdHM6DQoNCmBgYHtyfQ0KDQpkZl9yZWxfc291cmNlX21lbHQgPC0gbWVsdChkZl9yZWxfc291cmNlLCBpZCA9InJlbGV2YW5jZSIpDQoNCnBsb3RfcmVsZXZhbmNlX3NvdXJjZSA8LSBnZ3Bsb3QoZGZfcmVsX3NvdXJjZV9tZWx0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4PXJlbGV2YW5jZSwgeT12YWx1ZSwgZ3JvdXAgPSB2YXJpYWJsZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSB2YXJpYWJsZSxzaGFwZT12YXJpYWJsZSApKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGltaXRzID0gcmV2KGxldmVscyhkZl9yZWxfc291cmNlX21lbHQkcmVsZXZhbmNlKSkpKw0KICAgIGdlb21fc2VnbWVudCggYWVzKHg9cmVsZXZhbmNlICx4ZW5kPXJlbGV2YW5jZSwgeT0wLCB5ZW5kPW1heCh2YWx1ZSkpLCBjb2xvcj0iZ3JleSIpICsNCiAgICBnZW9tX3BvaW50KHNpemU9NywgYWxwaGEgPSAwLjgpICsNCiAgICBjb29yZF9mbGlwKCkgKw0KICAgICMgdGhlbWVfaXBzdW0oKSArDQogICAgdGhlbWUoDQogICAgICBwYW5lbC5ncmlkLm1pbm9yLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICBsZWdlbmQucG9zaXRpb249InRvcCINCiAgICApICsNCiAgICB4bGFiKCJRdWVyeSBJRCIpKw0KICAgIHlsYWIoInB1YmxpY2F0aW9ucyBjb3VudHMiKSArDQogICAgI3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJncmF5NjAiLCAicmVkIiwicHVycGxlMiIsICJkb2RnZXJibHVlIiwgImJsYWNrIiwgImJsYWNrIikpKw0KICAgIyBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygxNSwgMTksIDE5LCAxOSwgMSwxMykpKw0KICAgIG15LnRoZW1lDQoNCg0KcGxvdF9yZWxldmFuY2Vfc291cmNlDQoNCiNnZ3NhdmUoInBsb3RzX3JlcG8vcGxvdF9yZWxldmFuY2Vfc291cmNlLnBuZyIsIGhlaWdodCA9IDcsIHdpZHRoID0gMTIpDQoNCmBgYA0KDQoNCg0KIyMgMi4gUXVlcmllcyBwZXJmb3JtYW5jZQ0KDQojIyMgMi4xLiBOIHB1YmxpY2F0aW9ucyBhbmQgcmVsZXZhbmNlIGNhdGVnb3JpZXMgcGVyIHF1ZXJ5DQoNCiMjIyMgR3JvdXBlZCBxdWVyaWVzDQoNCmBgYHtyfQ0KDQpkYXRhc2V0JGlkX3F1ZXJ5IDwtIGFzLmZhY3RvcihkYXRhc2V0JGlkX3F1ZXJ5KQ0KZGZfcXVlcmllc19jb3VudHMgPC0gY29tcHV0ZV9kZl9uX3JlbGV2YW5jZV9xdWVyaWVzKGRhdGEgPSBkYXRhc2V0KQ0KDQpkZl9xdWVyaWVzX2NvdW50cw0KYGBgDQoNClBsb3QgdGhlIHJlc3VsdHM6DQpgYGB7cn0NCmRmX3F1ZXJpZXNfY291bnRzX2FsbCA8LSBkYXRhc2V0ICU+JSANCiAgZ3JvdXBfYnkoZGF0YXNldF9yZWxldmFuY2UsIGlkX3F1ZXJ5KSAlPiUgDQogIHN1bW1hcmlzZShuID0gbigpKSANCg0KZGZfcXVlcmllc19jb3VudHNfYWxsIDwtIGRmX3F1ZXJpZXNfY291bnRzX2FsbCAlPiUgZHBseXI6OmZpbHRlcihkYXRhc2V0X3JlbGV2YW5jZSAlaW4lIGMoIlgiLCAiTCIsICJNIiwgIkgiKSkgJT4lIG11dGF0ZShkYXRhc2V0X3JlbGV2YW5jZSA9IGZhY3RvcihkYXRhc2V0X3JlbGV2YW5jZSkpDQoNCnBsb3RfZ3JvdXAucXVlcmllc19yZWxldmFuY2UgIDwtIGdncGxvdChkZl9xdWVyaWVzX2NvdW50c19hbGwsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHg9aWRfcXVlcnksIHk9biwgZ3JvdXAgPSBkYXRhc2V0X3JlbGV2YW5jZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBkYXRhc2V0X3JlbGV2YW5jZSxzaGFwZT1kYXRhc2V0X3JlbGV2YW5jZSApKSArDQogIGdlb21fc2VnbWVudCggYWVzKHg9aWRfcXVlcnkgLHhlbmQ9aWRfcXVlcnksIHk9MCwgeWVuZD1tYXgobikpLCBjb2xvcj0iZ3JleSIpICsNCiAgICBnZW9tX3BvaW50KHNpemU9NCwgYWxwaGEgPSAwLjgpICsNCiAgICBjb29yZF9mbGlwKCkgKw0KICAgICMgdGhlbWVfaXBzdW0oKSArDQogICAgdGhlbWUoDQogICAgICBwYW5lbC5ncmlkLm1pbm9yLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICBsZWdlbmQucG9zaXRpb249InRvcCINCiAgICApICsNCiAgICB4bGFiKCJRdWVyeSBJRCIpKw0KICAgIHlsYWIoInB1YmxpY2F0aW9ucyBjb3VudHMiKSArDQogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoICJyZWQiLCJwdXJwbGUyIiwgImRvZGdlcmJsdWUiLCAiYmxhY2siKSkrDQogICAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcyA9IGMoMTksIDE5LCAxOSwgMSkpKw0KICAgIG15LnRoZW1lDQoNCnBsb3RfZ3JvdXAucXVlcmllc19yZWxldmFuY2UgDQojZ2dzYXZlKCJwbG90c19yZXBvL3Bsb3RfZ3JvdXAucXVlcmllc19yZWxldmFuY2VfYWxsLnBuZyIsIGhlaWdodCA9IDcsIHdpZHRoID0gMTIpDQpgYGANCg0KLSAgIFF1ZXJ5IDQgZGVsaXZlcnMgdGhlIGhpZ2hlc3QgbnVtYmVyIG9mIHJlbGV2YW50IGRhdGFzZXRzIChoaWdoZXN0IG51bWJlciBvZiBIaWdoLCBNb2RlcmF0ZSBhbmQgTG93IHJlbGV2YW5jZSBkYXRhc2V0cyksIGJ1dCBhbHNvIHJldHJpZXZlcyBieSBmYXIgdGhlIGhpZ2hlciBhbW91bnQgb2YgcHVibGljYXRpb24uDQoNCi0gICBTb21lIHF1ZXJpZXMgZG8gbm90IGFkZCBhbnkgcmVsZXZhbnQgcHVibGljYXRpb24gKGUuZy4gIjAsNiw1LDciIG9yICI3LDIsOSIpLg0KDQoNCiMjIyMgSW5kaXZpZHVhbCBxdWVyaWVzDQoNCmBgYHtyfQ0KDQpjb252ZXJ0X3F1ZXJ5ID0gZGF0YS5mcmFtZSgiaWRfcXVlcnkiID0gYXMuY2hhcmFjdGVyKHNlcSgwLDEwKSksICJxdWVyeSIgPSBjKA0KInN1cnZleSArIHNwZWNpZXMiLA0KInRpbWUgc2VyaWVzICsgc3BlY2llcyIsDQoiaW52ZW50b3J5ICsgc3BlY2llcyIsDQoic3BlY2llcyIsDQoiYWJ1bmRhbmNlICsgc3BlY2llcyIsDQoib2NjdXJyZW5jZSArIHNwZWNpZXMiLA0KInBvcHVsYXRpb24gKyBzcGVjaWVzIiwNCiJzaXRlcyArIHNwZWNpZXMiLA0KInNhbXBsaW5nICsgc3BlY2llcyIsDQoiY29sbGVjdGlvbiArIHNwZWNpZXMiLA0KImRlbnNpdHkgKyBzcGVjaWVzIg0KICANCikpDQoNCg0KZGF0YXNldF9xDQpgYGANCg0KYGBge3J9DQoNCiNzcGxpdCByb3dzIGluIGZ1bmN0aW9uIG9mIGNoYXJhY3RlciBjb21hcw0KDQpkYXRhc2V0X3EgPC0gZGF0YXNldCAlPiUgICAgICAgICAgICAgICANCiAgc2VwYXJhdGVfcm93cyhpZF9xdWVyeSwgc2VwPSIsIikgDQpkYXRhc2V0X3EgPC0gbWVyZ2UoZGF0YXNldF9xLCBjb252ZXJ0X3F1ZXJ5LCBieS5hbGw9ImlkX3F1ZXJ5IikNCg0KZGF0YXNldF9xJHF1ZXJ5ICA8LSBmYWN0b3IoZGF0YXNldF9xJHF1ZXJ5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPSBjKCJzcGVjaWVzIiwgIm9jY3VycmVuY2UgKyBzcGVjaWVzIiwgImludmVudG9yeSArIHNwZWNpZXMiLCAiY29sbGVjdGlvbiArIHNwZWNpZXMiLCAic2FtcGxpbmcgKyBzcGVjaWVzIiwgInN1cnZleSArIHNwZWNpZXMiICwgInBvcHVsYXRpb24gKyBzcGVjaWVzIiwgInNpdGVzICsgc3BlY2llcyIsICJkZW5zaXR5ICsgc3BlY2llcyIsICJhYnVuZGFuY2UgKyBzcGVjaWVzIiAsICJ0aW1lIHNlcmllcyArIHNwZWNpZXMiICkpDQpkYXRhc2V0X3ENCg0KYGBgDQpgYGB7cn0NCmRmX3F1ZXJpZXNfY291bnRzX2luZCA8LSBkYXRhc2V0X3EgJT4lIA0KICBncm91cF9ieShkYXRhc2V0X3JlbGV2YW5jZSwgcXVlcnkpICU+JSANCiAgc3VtbWFyaXNlKG4gPSBuKCkpIA0KDQpkZl9xdWVyaWVzX2NvdW50c19pbmQgPC0gZGZfcXVlcmllc19jb3VudHNfaW5kICU+JSBkcGx5cjo6ZmlsdGVyKGRhdGFzZXRfcmVsZXZhbmNlICVpbiUgYygiWCIsICJMIiwgIk0iLCAiSCIpKSAlPiUgbXV0YXRlKGRhdGFzZXRfcmVsZXZhbmNlID0gZmFjdG9yKGRhdGFzZXRfcmVsZXZhbmNlKSkNCg0KcGxvdF9xdWVyaWVzX3JlbGV2YW5jZV9jb3VudHMgPC0gZ2dwbG90KGRmX3F1ZXJpZXNfY291bnRzX2luZCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeD1xdWVyeSwgeT1uLCBncm91cCA9IGRhdGFzZXRfcmVsZXZhbmNlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGRhdGFzZXRfcmVsZXZhbmNlLHNoYXBlPWRhdGFzZXRfcmVsZXZhbmNlICkpICsNCiAgZ2VvbV9zZWdtZW50KCBhZXMoeD1xdWVyeSAseGVuZD1xdWVyeSwgeT0wLCB5ZW5kPW1heChuKSksIGNvbG9yPSJncmV5IikgKw0KICAgIGdlb21fcG9pbnQoc2l6ZT00LCBhbHBoYSA9IDAuOCkgKw0KICAgIGNvb3JkX2ZsaXAoKSArDQogICAgIyB0aGVtZV9pcHN1bSgpICsNCiAgICB0aGVtZSgNCiAgICAgIHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgIGxlZ2VuZC5wb3NpdGlvbj0idG9wIg0KICAgICkgKw0KICAgIHhsYWIoIlF1ZXJ5IElEIikrDQogICAgeWxhYigicHVibGljYXRpb25zIGNvdW50cyIpICsNCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyggInJlZCIsInB1cnBsZTIiLCAiZG9kZ2VyYmx1ZSIsICJibGFjayIpKSsNCiAgICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygxOSwgMTksIDE5LCAxKSkrDQogICAgbXkudGhlbWUNCg0KcGxvdF9xdWVyaWVzX3JlbGV2YW5jZV9jb3VudHMNCg0KDQpgYGANCg0KLSBxdWVyeSA0IHNob3dzIHRoZSBoaWdoZXN0IG51bWJlciBvZiByZWxldmFudCBkYXRhc2V0cyBmb3IgaGlnaCwgbW9kZXJhdGUgYW5kIGxvdyByZWxldmFuY2UgZGF0YXNldHMsIGJ1dCByZXRyaWV2ZXMgYWxtb3N0IGRvdWJsZSBudW1iZXIgb2YgYXJ0aWNsZXMgKH43MCkgY29tcGFyZWQgdG8gdGhlIHJlc3Qgb2YgcXVlcmllcy4NCg0KLSBxdWVyaWVzIDUgYW5kIDcgYXJlIGFibGUgdG8gcmV0cmlldmUgc29tZSByZWxldmFudCBkYXRhc2V0cyB3aXRoIH4yNSBwdWJsaWNhdGlvbnMgcmV0cmlldmVkLg0KDQoNCiMjIyAyLjIgQ2xhc3NpZnkgcXVlcmllcyBieSB0aGVpciByZWxldmFuY2UgKGluZGV4KQ0KDQpQdWN0dWF0aW9ucyBmb3IgdGhlIHJlbGV2YW5jZSBjYXRlZ29yaWVzOg0KDQotICAgSGlnaCA9IDUNCi0gICBNb2QgPSAyDQotICAgTG93ID0gMC41DQotICAgbm9uX3JlbGV2YW50ID0gMA0KDQoqSW5kZXggMSo6IGlzIHRoZSByZXN1bHQgb2Ygc3VtbWluZyB0aGUgcHVudHVhdGlvbnMgYXNzaWduZWQgdG8gZWFjaCByZWxldmFuY2UgY2F0ZWdvcnkNCg0KSW5kZXggMSA9IHN1bSBwdW5jdHVhdGlvbnMgPSAoTiBIaWdoIFwqIDUpICsgKE4gTW9kIFwqIDIpICsgKE4gTG93IFwqIDAuNSkNCg0KKkluZGV4IDIqOiBpcyB0aGUgSW5kZXgxIGJ1dCBhY2NvdW50aW5nIGZvciB0aGUgdG90YWwgbnVtYmVyIG9mIHB1YmxpY2F0aW9ucyByZXRyaWV2ZWQgYnkgdGhlIHF1ZXJ5Lg0KDQpJbmRleCAyID0gc3VtIHB1bmN0dWF0aW9ucyAvIG4gcHVibGljYXRpb25zIHJldHJpZXZlZA0KDQoNCiMjIyMgR3JvdXBlZCBxdWVyaWVzDQoNCmBgYHtyfQ0KDQpkZl9xdWVyaWVzX2NvdW50c19pbmRleCA8LSBjb21wdXRlX2luZGV4MTIoZGZfY291bnRzID0gZGZfcXVlcmllc19jb3VudHMsIG9yZGVyX2Jhc2VkX2luZGV4ID0gIjIiKSAjICIxIiBmb3Igb3JkZXJpbmcgYWNjb3JkaW5nIHRvIGluZGV4IDEsICIyIiBmb3IgaW5kZXggMg0KDQpkZl9xdWVyaWVzX2NvdW50c19pbmRleA0KDQpgYGANCg0KUXVlcmllcyAzLCA0LCAwLCBhbmQgNiBhcmUgdGhlIHRvcCA0IGluZGV4MSB2YWx1ZXMuIFF1ZXJpZXMgMyBhbmQgNCBoYXZlIGZhciBoaWdoZXIgaW5kZXggdGhhbiB0aGUgcmVzdC4NCg0KUGxvdCB0aGUgcmVzdWx0czoNCg0KYGBge3J9DQoNCnBsb3RfZ3JvdXAucXVlcmllc19pbmRleDEyIDwtIHBsb3RfcXVlcmllc19pbmRleChkZiA9IGRmX3F1ZXJpZXNfY291bnRzX2luZGV4KQ0KDQpwbG90X2dyb3VwLnF1ZXJpZXNfaW5kZXgxMg0KDQojZ2dzYXZlKCJwbG90c19yZXBvL3Bsb3RfZ3JvdXAucXVlcmllc19pbmRleDEyLnBuZyIsIGhlaWdodCA9IDUsIHdpZHRoID0gMTEpDQpgYGANCg0KLSAgIEluZGV4IDEgc2hvd3MgdGhhdCBxdWVyaWVzICIzIiBhbmQgIjQiIG9mZmVyIHRoZSBoaWdoZXIgcGVyZm9tYW5jZSwgZm9sbG93ZWQgYnkgIjYiIGFuZCAiNSIuDQoNCi0gICBJbmRleCAyIHNob3dzIHRoYXQgcXVlcmllICI2LDcsNSIgb2ZmZXIgdGhlIGhpZ2hlc3QgcGVyZm9ybWFuY2UsIGZvbGxvd2VkIGJ5ICI3LDgiLCAiNiw3IiwgYW5kICI1LDYsNyw4LDkiDQoNCg0KIyMjIyBpbmRpdmlkdWFsIHF1ZXJpZXMNCg0KYGBge3J9DQpkZl9xdWVyaWVzX2NvdW50c19pbmRleF9pbmQgPC0gY29tcHV0ZV9pbmRleDEyKGRmX2NvdW50cyA9IGRmX3F1ZXJpZXNfY291bnRzX2luZCwgb3JkZXJfYmFzZWRfaW5kZXggPSAiMiIpICMgIjEiIGZvciBvcmRlcmluZyBhY2NvcmRpbmcgdG8gaW5kZXggMSwgIjIiIGZvciBpbmRleCAyDQoNCmRmX3F1ZXJpZXNfY291bnRzX2luZGV4X2luZA0KYGBgDQoNCg0KUGxvdCB0aGUgcmVzdWx0cw0KDQpgYGB7cn0NCnBsb3RfcXVlcmllc19pbmRleDEyX2luZCA8LSBwbG90X3F1ZXJpZXNfaW5kZXgoZGYgPSBkZl9xdWVyaWVzX2NvdW50c19pbmRleF9pbmQpDQoNCnBsb3RfcXVlcmllc19pbmRleDEyX2luZA0KDQojZ2dzYXZlKCJwbG90c19yZXBvL3Bsb3RfaW5kLnF1ZXJpZXNfaW5kZXgxMi5wbmciLCBoZWlnaHQgPSA1LCB3aWR0aCA9IDExKQ0KYGBgDQoNCg0KDQojIyMgMi4zLiBGIFNDT1JFDQoNClByZWNpc2lvbg0KUmVjYWxsDQoNCkJpbmFyeSB2aWV3OiBNLEggcmVsZXZhbnQgfCBMLFggdW5yZXZlbGFudA0KDQoNCk4gcmVsZXZhbnQgZGF0YXNldHMgLT4gVHJ1ZSBQb3NpdGl2ZXMgKFRQKQ0KDQpOIHVucmVsZXZhbnQgZGF0YXNldHMgLT4gRmFsc2UgUG9zaXRpdmVzIChGUCkNCg0KU3VtIG9mIGFsbCByZWxldmFudCBwdWJsaWNhdGlvbnMgb2YgdGhlIGRhdGFzZXQgdGhhdCBhcmUgbm90IGRldGVjdGVkID0gRk4NCg0KUFJFQ0lTSU9OID0gVFAgLyBUUCArIEZQDQoNClJFQ0FMTCA9IFRQIC8gVFAgKyBGTg0KDQoNCkYgU0NPUkUgPSAyKiBwcmVjaXNpb24gKiByZWNhbGwgLyBwcmVjaXNpb24gKyByZWNhbGwNCg0KDQoNClNwbGl0IGdyb3VwZWQgcXVlcmllcyBpbnRvIGluZGl2aWR1YWwgb25lcw0KDQpgYGB7cn0NCg0KZGZfenNjb3Jlc19xdWVyaWVzIDwtIGNhbGN1bGF0ZV96LnNjb3JlX3F1ZXJpZXMoZGYgPSBkYXRhc2V0KQ0KDQpkZl96c2NvcmVzX3F1ZXJpZXMNCg0KYGBgDQoNClBsb3QgcmVzdWx0cw0KDQpgYGB7cn0NCnBsb3RfcXVlcmllc19zY29yZXMgPC0gZ2dwbG90KGRmX3pzY29yZXNfcXVlcmllcywgYWVzKHggPSBQcmVjaXNpb24sIHkgPSBSZWNhbGwsIGNvbG91ciA9IEZzY29yZSkpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMTApKw0KICBnZW9tX2xhYmVsX3JlcGVsKGFlcyhsYWJlbCA9IHF1ZXJpZXMpKSsNCiAgdGhlbWVfYncoKSsNCiAgbXkudGhlbWUNCg0KcGxvdF9xdWVyaWVzX3Njb3Jlcw0KDQojZ2dzYXZlKCJwbG90c19yZXBvL3Bsb3RfcXVlcmllc19zY29yZXMucG5nIiwgaGVpZ2h0ID0gNywgd2lkdGggPSA3KQ0KYGBgDQoNCg0KDQoNCiMjIDMuIFRlbXBvcmFsIGR1cmF0aW9uDQoNCiMjIyAzLjEgVGVtcG9yYWwgZHVyYXRpb24gY291bnRzDQoNCkhvdyBtYW55IHB1YmxpY2F0aW9ucyB3aXRob3V0IHRlbXBvcmFsIGR1cmF0aW9uIGRhdGE/DQoNCmBgYHtyfQ0Kbk5hIDwtIGNvdW50X25vdC5yZXBvcnRlZF90ZW1wb3JhbC5kdXJhdGlvbihkYXRhc2V0KQ0KDQpwcmludChwYXN0ZShuTmEsICJwdWJsaWNhdGlvbnMgd2l0aG91dCB0ZW1wb3JhbCBkdXJhdGlvbiBkYXRhIikpDQoNCmBgYA0KDQpQdWJsaWNhdGlvbiBjb3VudHMgYnkgdGVtcG9yYWwgZHVyYXRpb24gKHllYXJzKToNCg0KYGBge3J9DQogIA0KZGZfZHVyYXRpb25fY291bnRzIDwtIGNvdW50X2R1cmF0aW9ucyhkZiA9IGRhdGFzZXQsIG9yZGVyX2J5ID0gImNvdW50cyIpDQoNCmRmX2R1cmF0aW9uX2NvdW50cw0KYGBgDQoNClBsb3QgdGhlIHJlc3VsdHM6DQoNCmBgYHtyfQ0KcGxvdF90ZW1wX2R1cmF0aW9uIDwtIHBsb3RfZHVyYXRpb25fY291bnRzKGRmID0gZGZfZHVyYXRpb25fY291bnRzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3VudHNfTmEgPSBuTmEpDQoNCnBsb3RfdGVtcF9kdXJhdGlvbg0KDQojZ2dzYXZlKCJwbG90c19yZXBvL3Bsb3RfdGVtcF9kdXJhdGlvbi5wbmciLCBoZWlnaHQgPSA3LCB3aWR0aCA9IDEyKQ0KDQpgYGANCg0KQXZlcmFnZSBkdXJhdGlvbiAoZm9yIHRob3NlIHdpdGggZGF0YSk6DQoNCmBgYHtyfQ0KDQpkYXRhc2V0X3RlbXBfZHVyYXRpb24gPC0gc3Vic2V0KGRhdGFzZXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlbXBvcmFsX2R1cmF0aW9uX3kgPiAwIHwgdGVtcG9yYWxfZHVyYXRpb25feSA9PSAibm8iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCA9IGModGVtcG9yYWxfZHVyYXRpb25feSxpZF9xdWVyeSkpDQogIA0KICBkYXRhc2V0X3RlbXBfZHVyYXRpb24kdGVtcG9yYWxfZHVyYXRpb25feSA8LSBhcy5udW1lcmljKGRhdGFzZXRfdGVtcF9kdXJhdGlvbiR0ZW1wb3JhbF9kdXJhdGlvbl95KQ0KICANCiAgDQoNCnByaW50KHBhc3RlKCJtZWFuIG9mIiwNCiAgICAgICAgICAgIHJvdW5kKG1lYW4obmEub21pdChkYXRhc2V0X3RlbXBfZHVyYXRpb24kdGVtcG9yYWxfZHVyYXRpb25feSkpLGRpZ2l0cyA9IDEpLA0KICAgICAgICAgICAgInllYXJzIikpDQpgYGANCg0KIyMjIDMuMSBUZW1wb3JhbCBkdXJhdGlvbiBjb3VudHMgcGVyIHJlbGV2YW5jZSBjYXRlZ29yeQ0KDQpgYGB7cn0NCg0KDQpwbG90X2R1cmF0aW9uX3JlbGV2YW5jZS5jYXRlZ29yaWVzIDwtIHBsb3RfZHVyYXRpb25fcmVsZXZhbmNlKGRmID0gZGF0YXNldCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY291bnRzX05hID0gbk5hKQ0KDQpwbG90X2R1cmF0aW9uX3JlbGV2YW5jZS5jYXRlZ29yaWVzDQoNCmdnc2F2ZSgicGxvdHNfcmVwby9wbG90X2R1cmF0aW9uX3JlbGV2YW5jZS5jYXRlZ29yaWVzLnBuZyIsIGhlaWdodCA9IDcsIHdpZHRoID0gOSkNCg0KYGBgDQoNCiMjIDQuIFNwYXRpYWwgcmFuZ2UNCg0KIyMjIDQuMSBjb3VudCBzcGF0aWFsIHJhbmdlIHB1YmxpY2F0aW9ucw0KDQpXQVJOSU5HOiBwdWJsaWNhdGlvbnMgdGhhdCBjYW50IGJlIGFjY2Vzc2VkIGFyZSBub3QgY291bnRlZA0KDQpIb3cgbWFueSBwdWJsaWNhdGlvbnMgd2l0aG91dCBzcGF0aWFsIHJhbmdlIGRhdGE/DQoNCmBgYHtyfQ0KDQpuX25vdF9yZXBvcnRlZCA8LSBjb3VudF9ub3QucmVwb3J0ZWRfc3BhdGlhbF9yYW5nZShkYXRhc2V0KQ0KDQpwcmludChwYXN0ZShuX25vdF9yZXBvcnRlZCwgInB1YmxpY2F0aW9ucyB3aXRob3V0IHNwYXRpYWwgcmFuZ2UgZGF0YSIpKQ0KDQpgYGANCg0KU3BhdGlhbCByYW5nZXMgYXJlIGRpdmlkZWQgYWNjb3JkaW5nIHRvIHRoZSB0aHJlc2hvbGRzIGVzdGFibGlzaGVkIHRvIGRldGVybWluZSBhIGxvdywgbW9yZWRhdGUgYW5kIGhpZ2ggc3BhdGlhbCByYW5nZTogXDw1MDAwLCA1MDAtMTUwMDAsIFw+MTUwMDANCg0KYGBge3J9DQoNCnBsb3Rfc3BhdGlhbF9yYW5nZV9jb3VudHMgPC0gcGxvdF9zcGF0LnJhbmdlX2NvdW50cyhkYXRhc2V0KQ0KDQpwbG90X3NwYXRpYWxfcmFuZ2VfY291bnRzDQoNCiNnZ3NhdmUoInBsb3RzX3JlcG8vcGxvdF9zcGF0aWFsX3JhbmdlX2NvdW50cy5wbmciLCBoZWlnaHQgPSA3LCB3aWR0aCA9IDcpDQoNCmBgYA0KDQpBdmVyYWdlIHNwYXRpYWwgcmFuZ2UgKGZvciB0aG9zZSB3aXRoIGRhdGEpOg0KDQpgYGB7cn0NCg0KZGF0YXNldDEgPC0gZGF0YXNldFtkYXRhc2V0JGRhdGFzZXRfcmVsZXZhbmNlICE9ICJjYW50IGFjY2VzcyIsXQ0KICANCiAgZGF0YXNldDEkZGF0YXNldF9yZWxldmFuY2UgPC0gYXMuZmFjdG9yKGRhdGFzZXQxJGRhdGFzZXRfcmVsZXZhbmNlKQ0KICANCiAgc3BhdGlhbF9yYW5nZV9rbTJfdmVjIDwtIGRhdGFzZXQxJHNwYXRpYWxfcmFuZ2Vfa20yW2RhdGFzZXQxJHNwYXRpYWxfcmFuZ2Vfa20yICE9ICIiXQ0KICANCiAgc3BhdGlhbF9yYW5nZV9rbTJfdmVjIDwtIHNwYXRpYWxfcmFuZ2Vfa20yX3ZlY1shaXMubmEoc3BhdGlhbF9yYW5nZV9rbTJfdmVjKV0NCiAgDQogIHNwYXRpYWxfcmFuZ2Vfa20yX3ZlYyA8LSBhcy5udW1lcmljKHNwYXRpYWxfcmFuZ2Vfa20yX3ZlYykgDQoNCg0KcHJpbnQocGFzdGUoIm1lYW4gb2YiLA0KICAgICAgICAgICAgcm91bmQobWVhbihuYS5vbWl0KHNwYXRpYWxfcmFuZ2Vfa20yX3ZlYykpLCBkaWdpdHMgPSAwKSwNCiAgICAgICAgICAgICJrbTIiKSkNCmBgYA0KDQojIyA1LiBUZW1wb3JhbCBkdXJhdGlvbiwgc3BhdGlhbCByYW5nZSwgcmVsZXZhbmNlDQoNCmBgYHtyfQ0KDQpwbG90X3NwYXRpYWxfdGVtcG9yYWxfcmVsZXZhbmNlIDwtIHBsb3Rfc3BhdF90ZW1wX3JlbGV2YW5jZShkZiA9IGRhdGFzZXQpDQoNCnBsb3Rfc3BhdGlhbF90ZW1wb3JhbF9yZWxldmFuY2UNCg0KI2dnc2F2ZSgicGxvdHNfcmVwby9wbG90X3NwYXRpYWxfdGVtcG9yYWxfcmVsZXZhbmNlLnBuZyIsIGhlaWdodCA9IDcsIHdpZHRoID0gOSkNCg0KYGBgDQoNCiMjIDYuIEVCViBkYXRhIHR5cGVzDQoNCmBgYHtyfQ0KDQpkZl9kYXRhX3R5cGVfY291bnRzIDwtIGNvbXB1dGVfZGZfZGF0YS50eXBlKGRmID0gZGF0YXNldCkNCg0KZGZfZGF0YV90eXBlX2NvdW50cw0KDQpgYGANCg0KUGxvdCB0aGUgcmVzdWx0cw0KDQpgYGB7cn0NCg0KcGxvdF9kYXRhX3R5cGUgPC0gcGxvdF9kYXRhLnR5cGVfY291bnRzKGRmX2RhdGFfdHlwZV9jb3VudHMpDQoNCnBsb3RfZGF0YV90eXBlDQoNCiNnZ3NhdmUoInBsb3RzX3JlcG8vcGxvdF9kYXRhX3R5cGUucG5nIiwgaGVpZ2h0ID0gNywgd2lkdGggPSA5KQ0KDQpgYGANCg0KIyMgNy4gRGF0YSBmb3JtYXQNCg0KDQpUaG9zZSBkYXRhc2V0cyBpbiB0aGUgcmVwb3NpdG9yeSB0aGF0IGFyZSByZWxldmFuY2UgY2F0ZWdvcnkgWCBkb24ndCBoYXZlIGZvcm1hdCBpbmZvcm1hdGlvbi4NCg0KYGBge3J9DQoNCnBsb3RfZGF0YV9mb3JtYXRfY291bnRzIDwtIHBsb3RfZGF0YS50eXBlX2Zvcm1hdChkYXRhc2V0KQ0KDQpwbG90X2RhdGFfZm9ybWF0X2NvdW50cw0KDQojZ2dzYXZlKCJwbG90c19yZXBvL3Bsb3RfZGF0YV9mb3JtYXRfY291bnRzLnBuZyIsIGhlaWdodCA9IDcsIHdpZHRoID0gOSkNCg0KYGBgDQoNCiMjIDguIERhdGFzZXRzIGFjY2Vzc2liaWxpdHkgYW5kIHJlbGV2YW5jZSBwZXIgeWVhcg0KDQpSYXcgcGxvdCByZWxldmFuY2UgcGVyIHllYXINCg0KYGBge3J9DQoNCnBsb3RfcmVsZXZhbmNlX2FjY2Vzc195ZWFyIDwtIHBsb3RfcmVsZXZhbmNlX3llYXIoZGF0YXNldCkNCg0KcGxvdF9yZWxldmFuY2VfYWNjZXNzX3llYXINCg0KI2dnc2F2ZSgicGxvdHNfcmVwby9wbG90X3JlbGV2YW5jZV9hY2Nlc3NfeWVhci5wbmciLCBoZWlnaHQgPSA3LCB3aWR0aCA9IDE0KQ0KDQpgYGANCg0KUmVsZXZhbmNlIHBlciB5ZWFyIChpbnRlcnZhbHMpOg0KDQpgYGB7cn0NCg0KZGZfcmVsZXZhbmNlX3llYXIucmFuZ2UgPC0gY29tcHV0ZV9kZl9yZWxldmFuY2VfeWVhci5yYW5nZShkYXRhc2V0KQ0KDQpkZl9yZWxldmFuY2VfeWVhci5yYW5nZQ0KDQpgYGANCg0KUGxvdCB0aGUgcmVzdWx0czoNCg0KYGBge3J9DQoNCnBsb3RfcmVsZXZhbmNlX3llYXIucmFuZ2UgPC0gcGxvdF9yZWxldmFuY2VfeWVhci5yYW5nZV9yZXBvKGRhdGFzZXQpDQoNCnBsb3RfcmVsZXZhbmNlX3llYXIucmFuZ2UNCg0KI2dnc2F2ZSgicGxvdHNfcmVwby9wbG90X3JlbGV2YW5jZV95ZWFyLnJhbmdlLnBuZyIsIGhlaWdodCA9IDcsIHdpZHRoID0gOSkNCg0KYGBgDQoNCg0KDQoNCiMjIDkuIEpvdXJuYWxzDQoNCg0KQ291bnQgcHVibGljYXRpb25zIHBlciBqb3VybmFsDQoNCmBgYHtyfQ0KDQpkYXRhc2V0LmogPC0gZGF0YXNldFtkYXRhc2V0JEpvdXJuYWwgIT0gIiIgJiBkYXRhc2V0JEpvdXJuYWwgIT0gIm5vIixdDQoNCmRmX2pvdXJuYWxzX2NvdW50cyA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGRhdGFzZXQuaiRKb3VybmFsKSkNCg0KY29sbmFtZXMoZGZfam91cm5hbHNfY291bnRzKSA9IGMoIkpvdXJuYWwiLCAiUHVibGljYXRpb25fY291bnRzIikNCg0KZGZfam91cm5hbHNfY291bnRzIDwtIGRmX2pvdXJuYWxzX2NvdW50c1tvcmRlcigtZGZfam91cm5hbHNfY291bnRzJFB1YmxpY2F0aW9uX2NvdW50cyksXQ0KDQoNCmRmX2pvdXJuYWxzX2NvdW50cw0KDQoNCmBgYA0KDQoNCg0KDQoNCg0KDQpgYGB7cn0NCg0KcHJpbnQocGFzdGUoIkEgdG90YWwgb2YiLCBsZW5ndGgoZGZfam91cm5hbHNfY291bnRzJEpvdXJuYWwpLCAiam91cm5hbHMiICkpDQoNCmBgYA0KDQpgYGB7cn0NCg0KYGBgDQoNCg0KIyMgMTAuIFNvdXJjZSBvZiBpbmZvcm1hdGlvbiBhY3Jvc3MgdGltZQ0KDQoNCmBgYHtyfQ0KDQpkZi5zb3VyY2VfdGltZSA8LSBkZl9zb3VyY2VfdGltZShkZiA9IGRhdGFzZXQpDQoNCmRmLnNvdXJjZV90aW1lDQoNCmBgYA0KDQoNCg0KDQoNCmBgYHtyfQ0KZGZfcGxvdF9zb3VyY2VfdGltZSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGRmLnNvdXJjZV90aW1lJHllYXIsZGYuc291cmNlX3RpbWUkc291cmNlICkpDQoNCmNvbG5hbWVzKGRmX3Bsb3Rfc291cmNlX3RpbWUpIDwtIGMoInllYXIiLCAic291cmNlIiwgIk4iKQ0KDQpkZl9wbG90X3NvdXJjZV90aW1lJE4gPC0gYXMubnVtZXJpYyhkZl9wbG90X3NvdXJjZV90aW1lJE4pDQoNCg0KcGxvdF9zb3VyY2VfdGltZSA8LSBnZ3Bsb3QoZGZfcGxvdF9zb3VyY2VfdGltZSwgYWVzKHggPSB5ZWFyLCB5ID0gTiwgZ3JvdXAgPSBzb3VyY2UsIGNvbG9yID0gc291cmNlKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEpKw0KICAgIHRoZW1lX2J3KCkrDQogIG15LnRoZW1lKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIGhqdXN0PTAuOTUsdmp1c3Q9MC4yLCBzaXplID0gOSkpKw0KICB5bGFiKCJOIHJldHJpZXZlZCBhcnRpY2xlcyIpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShndWlkZSA9IGd1aWRlX2F4aXMobi5kb2RnZT0yKSkNCg0KcGxvdF9zb3VyY2VfdGltZQ0KDQoNCiNnZ3NhdmUoInBsb3RzX3JlcG8vcGxvdF9zb3VyY2VfdGltZS5wbmciLCBoZWlnaHQgPSA3LCB3aWR0aCA9IDEzKQ0KDQpgYGANCg0KDQoNCiMjIDEwLiBMb2NhdGlvbiBvZiBpbmZvcm1hdGlvbg0KDQpgYGB7cn0NCg0KZGZfbG9jYXRpb25faW5mbyA8LSBjb21wdXRlX2RmX2xvY2F0aW9uX2luZm8oZGYgPSBkYXRhc2V0KQ0KDQpgYGANCg0KDQpgYGB7cn0NCmRmX3Bsb3RfbG9jX3NwYXRpYWwucmFuZ2UgPC0gZGZfbG9jYXRpb25faW5mb1tkZl9sb2NhdGlvbl9pbmZvJHNwYXRpYWxfcmFuZ2VfcG9zaXRpb24gIT0gIiIsXQ0KDQpkZl9wbG90X2xvY190ZW1wb3JhbC5yYW5nZSA8LSBkZl9sb2NhdGlvbl9pbmZvW2RmX2xvY2F0aW9uX2luZm8kdGVtcG9yYWxfcmFuZ2VfcG9zaXRpb24gIT0gIiIsXQ0KDQpkZl9wbG90X2xvY190ZW1wb3JhbC5kdXJhdGlvbiA8LSBkZl9sb2NhdGlvbl9pbmZvW2RmX2xvY2F0aW9uX2luZm8kdGVtcG9yYWxfZHVyYXRpb25fcG9zaXRpb24gIT0gIiIsXQ0KDQoNCg0KcGxvdF9zcGF0X3JhbmdlX3Bvc2l0aW9uIDwtIGdncGxvdChuYS5vbWl0KGRmX3Bsb3RfbG9jX3NwYXRpYWwucmFuZ2UpLCBhZXMoeD1zcGF0aWFsX3JhbmdlX3Bvc2l0aW9uLCBmaWxsID0gZGF0YXNldF9sb2NhdGlvbikpICsgDQogIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikpKSArDQogIGdlb21fdGV4dChzdGF0PSdjb3VudCcsIGFlcyhsYWJlbD0uLmNvdW50Li4pLHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpKSsNCiAgZ2d0aXRsZSgiU3BhdGlhbCByYW5nZSBpbmZvcm1hdGlvbiIpKw0KICB0aGVtZV9idygpKw0KICBteS50aGVtZSsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdD0wLjk1LHZqdXN0PTAuMiwgc2l6ZSA9IDkpKSsNCiAgeWxhYigiTiByZXRyaWV2ZWQgYXJ0aWNsZXMiKSArDQogIHNjYWxlX3hfZGlzY3JldGUoZ3VpZGUgPSBndWlkZV9heGlzKG4uZG9kZ2U9MikpDQoNCg0KcGxvdF90ZW1wX3JhbmdlX3Bvc2l0aW9uIDwtIGdncGxvdChuYS5vbWl0KGRmX3Bsb3RfbG9jX3RlbXBvcmFsLnJhbmdlKSwgYWVzKHg9dGVtcG9yYWxfcmFuZ2VfcG9zaXRpb24sIGZpbGwgPSBkYXRhc2V0X2xvY2F0aW9uKSkgKyANCiAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKSkpICsNCiAgZ2VvbV90ZXh0KHN0YXQ9J2NvdW50JywgYWVzKGxhYmVsPS4uY291bnQuLikscG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSkpKw0KICAgIGdndGl0bGUoIlRlbXBvcmFsIHJhbmdlIGluZm9ybWF0aW9uIikrDQogIHRoZW1lX2J3KCkrDQogIG15LnRoZW1lKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIGhqdXN0PTAuOTUsdmp1c3Q9MC4yLCBzaXplID0gOSkpKw0KICB5bGFiKCJOIHJldHJpZXZlZCBhcnRpY2xlcyIpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShndWlkZSA9IGd1aWRlX2F4aXMobi5kb2RnZT0yKSkNCg0KDQpkZl9wbG90X2xvY190ZW1wb3JhbC5kdXJhdGlvbiA8LSBnZ3Bsb3QobmEub21pdChkZl9wbG90X2xvY190ZW1wb3JhbC5kdXJhdGlvbiksIGFlcyh4PXRlbXBvcmFsX2R1cmF0aW9uX3Bvc2l0aW9uLCBmaWxsID0gZGF0YXNldF9sb2NhdGlvbikpICsgDQogIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikpKSArDQogIGdlb21fdGV4dChzdGF0PSdjb3VudCcsIGFlcyhsYWJlbD0uLmNvdW50Li4pLHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpKSsNCiAgZ2d0aXRsZSgiVGVtcG9yYWwgZHVyYXRpb24gaW5mb3JtYXRpb24iKSsNCiAgdGhlbWVfYncoKSsNCiAgbXkudGhlbWUrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgaGp1c3Q9MC45NSx2anVzdD0wLjIsIHNpemUgPSA5KSkrDQogIHlsYWIoIk4gcmV0cmlldmVkIGFydGljbGVzIikgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGd1aWRlID0gZ3VpZGVfYXhpcyhuLmRvZGdlPTIpKQ0KDQoNCnBsb3RfbG9jYXRpb25fc3BhdGlvdGVtcG9yYWxfaW5mb3JtYXRpb24gPC0gZ2dhcnJhbmdlKHBsb3Rfc3BhdF9yYW5nZV9wb3NpdGlvbiwNCiAgICAgICAgICBwbG90X3RlbXBfcmFuZ2VfcG9zaXRpb24sDQogICAgICAgICAgZGZfcGxvdF9sb2NfdGVtcG9yYWwuZHVyYXRpb24sDQogICAgICAgICAgbmNvbCA9IDEsDQogICAgICAgICAgbnJvdyA9IDMsIA0KICAgICAgICAgIGNvbW1vbi5sZWdlbmQgPSBUUlVFLA0KICAgICAgICAgIGxlZ2VuZCA9ICJ0b3AiKQ0KDQoNCnBsb3RfbG9jYXRpb25fc3BhdGlvdGVtcG9yYWxfaW5mb3JtYXRpb24NCg0KDQojZ2dzYXZlKCJwbG90c19yZXBvL3Bsb3RfbG9jYXRpb25fc3BhdGlvdGVtcG9yYWxfaW5mb3JtYXRpb24ucG5nIiwgaGVpZ2h0ID0gMTYsIHdpZHRoID0gNykNCg0KYGBgDQoNCg0KDQoNCiMgbG9jYXRpb24gLSBkYXRhc2V0IHR5cGUNCg0KDQpXZSBldmFsdWF0ZSB0aGUgZmVhc2liaWxpdHkgdG8gYXV0b21hdGljYWxseSByZXRyaWV2ZSB0aGUgaW5mb3JtYXRpb24gb24gdGhlIGRhdGFzZXQgdHlwZSBieSBsb29raW5nIGF0IHdoZXRoZXIgYXV0aG9ycyBtYWtlIGV4cGxpY2l0IHRoZSB0eXBlIG9mIGRhdGFzZXQgaW4gZWl0aGVyIHRoZSB0aXRsZSBvciBhYnN0cmFjdC4NCg0KVHlwZSBvZiBkYXRhc2V0IHNob3VsZCBmYWxsIGluIG9uZSBvZiB0aGUgZm9sbG93aW5nIGNhdGVnb3JpZXMgb3Igc3lub255bXM6DQoNCmBgYHtyfQ0KZGZfZGF0YXNldF90eXBlcyA8LSByZWFkLmNzdigiLi4vZGF0YS9kYXRhc2V0X3R5cGVzLmNzdiIsIHNlcCA9ICI7IikNCmRmX2RhdGFzZXRfdHlwZXMNCmBgYA0KDQpJIHdpbGwgYWRkIHRoZSBmb2xsb3dpbmcgc3lub255bXM6DQoNCnByZXNlbmNlLWFic2VuY2U6IGRldGVjdGlvbiwgY2FwdHVyZQ0KDQpFQlYgZ2VuZXRpYyBhbmFseXNlczogMTZTLCAxOFMsIGdlbmV0aWMgZGF0YSwgbWljcm9zYXRlbGl0ZXMsIGJhcmNvZGVzKD8pLCBoYXBsb3R5cGVzLCBlRE5BLCBTTlBzDQoNCg0KYGBge3J9DQpkZl9kYXRhc2V0X3R5cGVzW25yb3coZGZfZGF0YXNldF90eXBlcykrMSxdIDwtIGMoInByZXNlbmNlLWFic2VuY2UiLCAiZGV0ZWN0aW9uIiwgInN5bm9ueW0iKQ0KZGZfZGF0YXNldF90eXBlc1tucm93KGRmX2RhdGFzZXRfdHlwZXMpKzEsXSA8LSBjKCJwcmVzZW5jZS1hYnNlbmNlIiwgImNhcHR1cmUiLCAic3lub255bSIpDQoNCmRmX2RhdGFzZXRfdHlwZXNbbnJvdyhkZl9kYXRhc2V0X3R5cGVzKSsxLF0gPC0gYygiRUJWIGdlbmV0aWMgYW5hbHlzZXMiLCAiMTZTIiwgIm1ldGhvZHMiKQ0KZGZfZGF0YXNldF90eXBlc1tucm93KGRmX2RhdGFzZXRfdHlwZXMpKzEsXSA8LSBjKCJFQlYgZ2VuZXRpYyBhbmFseXNlcyIsICIxOFMiLCAibWV0aG9kcyIpDQpkZl9kYXRhc2V0X3R5cGVzW25yb3coZGZfZGF0YXNldF90eXBlcykrMSxdIDwtIGMoIkVCViBnZW5ldGljIGFuYWx5c2VzIiwgImJhcmNvZGVzIiwgIm1ldGhvZHMiKQ0KZGZfZGF0YXNldF90eXBlc1tucm93KGRmX2RhdGFzZXRfdHlwZXMpKzEsXSA8LSBjKCJFQlYgZ2VuZXRpYyBhbmFseXNlcyIsICJoYXBsb3R5cGVzIiwgIm1ldGhvZHMiKQ0KZGZfZGF0YXNldF90eXBlc1tucm93KGRmX2RhdGFzZXRfdHlwZXMpKzEsXSA8LSBjKCJFQlYgZ2VuZXRpYyBhbmFseXNlcyIsICJlRE5BIiwgIm1ldGhvZHMiKQ0KZGZfZGF0YXNldF90eXBlc1tucm93KGRmX2RhdGFzZXRfdHlwZXMpKzEsXSA8LSBjKCJFQlYgZ2VuZXRpYyBhbmFseXNlcyIsICJTTlBzIiwgIm1ldGhvZHMiKQ0KDQpkZl9kYXRhc2V0X3R5cGVzDQpgYGANCg0KDQoNCkRldGVjdGluZyB0aGUgd29yZHMgaW4gdGl0bGUgb3IgYWJzdHJhY3QNCg0KDQoNCmBgYHtyfQ0KDQoNCmRhdGFzZXQxIDwtIGRhdGFzZXRbLXdoaWNoKGlzLm5hKGRhdGFzZXRbLCJkZXNjcmlwdGlvbiJdKSksXQ0KZGF0YXNldDIgPC0gZGF0YXNldDFbLXdoaWNoKGRhdGFzZXQxJHRpdGxlID09ICIiKSxdDQoNCnVybCA8LSBjKCkNCmRhdGFzZXRfdHlwZSA8LSBjKCkNCmtleXdvcmRfYWJzIDwtIGxpc3QoKQ0Ka2V5d29yZF90aXRsZSA8LSBsaXN0KCkNCiNrZXl3b3JkX3R5cGUgPC0gYygpDQppbl90aXRsZSA8LSBjKCkNCmluX2Fic3RyYWN0IDwtIGMoKQ0KDQoNCg0KZm9yIChpIGluIDE6bnJvdyhkYXRhc2V0MikpIHsNCiAgDQogIA0KICANCiAgZGF0YXNldF90eXBlW2ldIDwtIGRhdGFzZXQyW2ksICJkYXRhX3R5cGUiXQ0KICB1cmxbaV0gPC0gZGF0YXNldDJbaSwgInVybCJdDQogIA0KICANCiAgDQogIA0KICAjIHNlYXJjaCBvbiB0aGUgYWJzdHJhY3QgYW5kIG5vdGUgdGhlIGxpc3Qgb2Yga2V5d29yZHMgdGhhdCBtYXRjaCB0aGUgZGF0YXNldCB0eXBlcw0KDQogIGtleXdvcmRfYWJzW1tpXV0gPC0gdW5pcXVlKGdldF9rZXl3b3JkcyhpbnB1dF9zdHJpbmcgPSBkYXRhc2V0MiRkZXNjcmlwdGlvbltpXSwgDQogICAgICAgICAgICAgICBkYXRhc2V0X3R5cGVzID0gIGRmX2RhdGFzZXRfdHlwZXMpKQ0KICANCiAgIyBpZiB0aGUgbGlzdCBvZiBrZXl3b3JkcyBoYXMgMSBvciBtb3JlIGVudHJ5LCB0aGVuIG5vdGUgdGhhdCB3ZSBmb3VuZCBpbmZvcm1hdGlvbiBpbiB0aGUgYWJzdHJhY3QNCiAgDQogIGlmKGxlbmd0aChrZXl3b3JkX2Fic1tpXVshc2FwcGx5KGtleXdvcmRfYWJzW2ldLCBpcy5udWxsKV0pID09IDApew0KICAgIA0KICAgIGluX2Fic3RyYWN0W2ldIDwtICJubyINCiAgICANCiAgfSBlbHNlIGlmIChsZW5ndGgoa2V5d29yZF9hYnNbaV1bIXNhcHBseShrZXl3b3JkX2Fic1tpXSwgaXMubnVsbCldKSA+IDApew0KICAgIA0KICAgIGluX2Fic3RyYWN0W2ldIDwtICJ5ZXMiDQogICAgDQogIH0NCiAgDQogIA0KICANCg0KICAjIHNlYXJjaCBvbiB0aGUgdGl0bGUgYW5kIG5vdGUgdGhlIGxpc3Qgb2Yga2V5d29yZHMgdGhhdCBtYXRjaCB0aGUgZGF0YXNldCB0eXBlcw0KDQogIGtleXdvcmRfdGl0bGVbW2ldXSA8LSB1bmlxdWUoZ2V0X2tleXdvcmRzKGlucHV0X3N0cmluZyA9IGRhdGFzZXQyJHRpdGxlW2ldLCANCiAgICAgICAgICAgICAgIGRhdGFzZXRfdHlwZXMgPSAgZGZfZGF0YXNldF90eXBlcykpDQogIA0KICAjIGlmIHRoZSBsaXN0IG9mIGtleXdvcmRzIGhhcyAxIG9yIG1vcmUgZW50cnksIHRoZW4gbm90ZSB0aGF0IHdlIGZvdW5kIGluZm9ybWF0aW9uIGluIHRoZSBhYnN0cmFjdA0KICANCiAgaWYobGVuZ3RoKGtleXdvcmRfdGl0bGVbaV1bIXNhcHBseShrZXl3b3JkX3RpdGxlW2ldLCBpcy5udWxsKV0pID09IDApew0KICAgIA0KICAgIGluX3RpdGxlW2ldIDwtICJubyINCiAgICANCiAgfSBlbHNlIGlmIChsZW5ndGgoa2V5d29yZF90aXRsZVtpXVshc2FwcGx5KGtleXdvcmRfdGl0bGVbaV0sIGlzLm51bGwpXSkgPiAwKXsNCiAgICANCiAgICBpbl90aXRsZVtpXSA8LSAieWVzIg0KICAgIA0KICB9IA0KICANCiAgDQogIA0KfQ0KDQoNCg0KDQojZGF0YS5mcmFtZShkYXRhc2V0X3R5cGUsIGtleXdvcmRfYWJzLCBrZXl3b3JkX3RpdGxlLCBpbl90aXRsZSwgaW5fYWJzdHJhY3QpDQoNCg0KZGZfZGF0YV90eXBlX2FicyA8LSBkYXRhLmZyYW1lKHVybCwgZGF0YXNldF90eXBlLCBpbl9hYnN0cmFjdCwgaW5fdGl0bGUpDQpkZl9kYXRhX3R5cGVfYWJzJGtleXdvcmRfYWJzIDwtIGtleXdvcmRfYWJzDQoNCiNlbGltaW5hdGUgdGhvc2UgZGF0YXNldHMgd2l0aG91dCBkYXRhX3R5cGUgaW5mbw0KDQpkZl9kYXRhX3R5cGVfYWJzMSA8LSBkZl9kYXRhX3R5cGVfYWJzW2RmX2RhdGFfdHlwZV9hYnMkZGF0YXNldF90eXBlICE9ICIiLF0NCg0KZGZfZGF0YV90eXBlX2FiczENCg0KDQpgYGANCg0KDQoNCkZvciBzb21lIHJlYXNvbiwga2V5d29yZF90aXRsZSBoYXMgMyBsZXNzIGVudHJpZXMgdGhhdCBrZXl3b3JkX2Fic3RyYWN0LCBhbmQgSSBjYW4ndCBmaWd1cmUgb3V0IHdoeS4gU28gZm9yIG5vdyBJIGNhbnQgb2J0YWluIHRoZSBsaXN0IG9mIHdvcmRzIHRoYXQgYXBwZWFyIGluIHRoZSB0aXRsZS4NCg0KDQpgYGB7cn0NCg0KcHJpbnQocGFzdGUobGVuZ3RoKHdoaWNoKGRmX2RhdGFfdHlwZV9hYnMxJGluX2Fic3RyYWN0ID09ICJ5ZXMiKSksIA0KICAgICAgICAgICAgIndpdGggZGF0YXNldCB0eXBlIG9yIHN5bm9ueW0gZXhwbGljaXQgaW4gdGhlIGFic3RyYWN0LCBvdXQgb2YiLCBsZW5ndGgoZGZfZGF0YV90eXBlX2FiczEkaW5fYWJzdHJhY3QpKSkNCg0KcHJpbnQocGFzdGUoInNvIiwgDQogICAgICByb3VuZChsZW5ndGgod2hpY2goZGZfZGF0YV90eXBlX2FiczEkaW5fYWJzdHJhY3QgPT0gInllcyIpKS9sZW5ndGgoZGZfZGF0YV90eXBlX2FiczEkaW5fYWJzdHJhY3QpKjEwMCwgMSksDQoiJSIpKQ0KDQoNCg0KYGBgDQoNCmBgYHtyfQ0KcHJpbnQocGFzdGUobGVuZ3RoKHdoaWNoKGRmX2RhdGFfdHlwZV9hYnMxJGluX3RpdGxlID09ICJ5ZXMiKSksIA0KICAgICAgICAgICAgIndpdGggZGF0YXNldCB0eXBlIG9yIHN5bm9ueW0gZXhwbGljaXQgaW4gdGhlIHRpdGxlLCBvdXQgb2YiLCBsZW5ndGgoZGZfZGF0YV90eXBlX2FiczEkaW5fdGl0bGUpKSkNCg0KcHJpbnQocGFzdGUoInNvIiwgDQogICAgICByb3VuZChsZW5ndGgod2hpY2goZGZfZGF0YV90eXBlX2FiczEkaW5fdGl0bGUgPT0gInllcyIpKS9sZW5ndGgoZGZfZGF0YV90eXBlX2FiczEkaW5fdGl0bGUpKjEwMCwgMSksDQoiJSIpKQ0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0K